  
var g_ProjectName=window.location.pathname.substring(window.location.pathname.indexOf("/")+1,window.location.pathname.lastIndexOf("/"));
var g_Prefix="https://super.4dage.com/";
// var g_Prefix=window.location.href.substring(0,window.location.href.indexOf("/index.html")+1);
var s = window.location.href.split('/');
    s.pop();
//var g_Prefix = s.join('/');
var g_index=null;
var g_modeldata=null;

var g_weixinTitle=null;

var g_Hots=null;
var g_HotMeshes=[];
var g_HotMeshSize = {
    g_HotMeshWidth: 0.3,
    g_HotMeshHeight:0.3
};





//add表示添加，delete表示删除
var g_HotStatus=null;
var g_newHot = [];//存储新加热点
var g_HotImage= {
	"point":"https://super.4dage.com/images/4dagePoint2.png",
	"point2":"https://super.4dage.com/images/4dagePoint.png"
};	
var g_saveHot=false;

var g_TextColor=0x7777ff;
var g_Text=null;
var g_TextPlaneMesh=[];
var g_TextIconMesh=[];
var g_TextIcon="./images/text.png";
var g_SelectTextIndex=null;
var g_TextShow=null;
var g_audioPlay=false;
var g_background=null; 
var g_roof=null;

var g_data2 = null;//加载的data2.js的内容
var g_bgAudio=null;//背景音乐
var g_tourAudio=null;//导览音乐
var g_play = 1;//表示播放图标状态
var g_playAudio = null;//当前在播放或当继续播放时应该播放的，是g_bgAudio或g_tourAudio 
var g_currentHot = null;//当前打开的热点 
//var g_Texture=null;
//var g_ChunknameTexture={};   //chunkname和贴图名称对应
var g_NormalTexture=false;
var g_SpecularTexture=false;
var g_DirectionalLight=null;  
  
var g_snapShotWidth = 200; //截图下载图片的大小
var g_snapShotHeight = 140;

//微信分享
var g_weixinObj = {
  "title": document.querySelector("head title").innerHTML,
  "lineLink" : window.location.href,
  "imgUrl" : "https://www.4dmodel.com/SuperPanoramic/images/weixintitle.jpg",
  "desc" : "四维时代提供技术支持",
}

var settings = {
    hotClickEvent:{
        video:{
            playAndPause:true,
            examine:false,
            openHot:false 
        },
        photo:{
            examine:false,
            openHot:false 
        },
        shine:{
            examine:true,
            openHot:true  
        }
    },
     
    hotClickActions:['playAndPause','examine','openHot','fastTran'],
   
    
    //默认的：
    teleportTime:  1500,//瞬间过渡的时间 
    /* flytimeDistanceMultiplier:150, 
    flyTime:750,  */
    tourRotTime:2,  //默认停留2秒
    //dontExamHot:true,  
    hotFastTran:false,
    transparentBg: false,
    bgImg:  null,
     
    tileClass:{//默认全景贴图加载的清晰度
        pc:{
            nav: '2k',
            max: '4k',
        },
        bigMobile:{ //width和height都超过一定值
            nav: '2k',
            max: '4k',//'2k',
        },
        mobile:{
            nav: '1k',  //不放大时
            max: '4k',  //放大最大
        }
    }, //可以稍后自行修改
}


if(window.number == '725'||window.number == '724'){
    //settings.mobileNavHigh = true 
    settings.tileClass.mobile = {nav:'2k', max: '2k'}
    
}


 
 


//共用函数：


window.common = null;  
window.MathLight = null;
window.math = null
window.easing = null
window.lerp = null
window.transitions = null
 



function watch(object, propName, initialValue){ //监听某个属性的变化
    let v = initialValue
    Object.defineProperty(object, propName, {
        get: function() {
            return v
        },
        set: function(e) {
             console.log('watch:',propName, e)
             v = e 
        }
    }) 
}
           


var toPrecision = function(e, t) {
    function i(e, t) {
        var i = Math.pow(10, t);
        return Math.round(e * i) / i
    }
    if (e instanceof Array) {
        for (var n = 0; n < e.length; n++)
            e[n] = i(e[n], t);
        return e
    }
    return i(e, t)
}


var dealMap = (map, o={} )=>{
    //使不resize  when   image is not power of two  ，但缩小时会有锯齿
    if(!o.ignoreResize){
        if(!map.image || !THREE.Math.isPowerOfTwo(map.image.width ) || !THREE.Math.isPowerOfTwo(map.image.height )){
            map.wrapS = map.wrapT = THREE.ClampToEdgeWrapping;
            map.minFilter = THREE.LinearFilter; 
            //map.generateMipmaps = true;
        } 
    }        
    if(!browser.isMobile()) map.anisotropy = 3
}  
 
 
  
var dom = { 
	getOffset: function(type, element, parent) { 
		left = (type == "left") ? element.offsetLeft : element.offsetTop;
		if (!parent) parent = $("body")[0];
		while (element = element.offsetParent) { 
			if (element == parent) break;
			left += (type == "left") ? element.offsetLeft : element.offsetTop;
		}
		return left;
	}  
};


var getTransformSid = function(){
    var name
    if(player.mode == 'panorama'){
        name = player.currentPano ? player.currentPano.id : 'outside'
    }else{
        name = 'outside'
    }
    return name
} 
 
var LineDraw = {

    /* createLine: function(posArr, o) {
        var e = new THREE.BufferGeometry
          , p = new Float32Array(6);

        e.addAttribute("position", new THREE.BufferAttribute(p,3));
        //这句新旧版写法不同  旧版这样：e.addAttribute("position",Float32Array,2,3);
        var p = e.attributes.position.array;
        for (var i = 0; i < 2; i++) {
            p[i * 3] = posArr[i].x;
            p[i * 3 + 1] = posArr[i].y;
            p[i * 3 + 2] = posArr[i].z;
        }
        var mat = new THREE[o.deshed ? "LineDashedMaterial" : "LineBasicMaterial"]({
            linewidth: o.width || 1,
            //windows无效。 似乎mac/ios上粗细有效 ？ 
            color: o.color || defaultColor,
            transparent: o.dontAlwaysSeen ? false : true,
            depthTest: o.dontAlwaysSeen ? true : false

        })
        var line = new THREE.Line(e,mat);
        line.renderOrder = o.renderOrder || 4
        //同tagStem;   //如果不加高，可能会部分被model遮住
        return line;

    } */
    
    
    createLine: function (posArr, o={}) {
        //多段普通线  （第二个点和第三个点之间是没有线段的, 所以不用在意线段顺序）
        var mat
        if(o.mat){
            mat = o.mat
        }else{
            let prop = {  
                color: o.color || defaultColor,
                transparent: o.dontAlwaysSeen ? false : true,
                depthTest: o.dontAlwaysSeen ? true : false, 
                opacity: o.opacity != void 0 ? o.opacity : 1,
            }
            if(o.deshed ){
                prop.dashSize = o.dashSize || 0.1,
                prop.gapSize = o.gapSize || 0.1
            }
            mat = new THREE[o.deshed ? "LineDashedMaterial" : "LineBasicMaterial"](prop) 
        }
         
        
        
        var line = new THREE.LineSegments(new THREE.BufferGeometry, mat);
		line.renderOrder = o.renderOrder || 4
  
        this.moveLine(line, posArr)
        
		return line;  

	},
    
    
	moveLine: function (line, posArr) {
        if(posArr.length == 0)return
        let position = new Float32Array(posArr.length * 3);  //[]
       
    
        for (var i = 0; i < 2; i++) {
            position[i * 3] = posArr[i].x;
            position[i * 3 + 1] = posArr[i].y;
            position[i * 3 + 2] = posArr[i].z;
        }
        line.geometry.addAttribute("position", new THREE.BufferAttribute(position,3)); 
        //line.geometry.setAttribute('position', new THREE.Float32BufferAttribute(new Float32Array(position), 3));
      
		line.geometry.attributes.position.needsUpdate = true;
		line.geometry.computeBoundingSphere();
        /* if(line.material instanceof THREE.LineDashedMaterial){
            line.computeLineDistances() //只有非buffer的geometry才有
        } */
	}  
	,
    
    
    
    
    
}

 
var convertTool = { 
	getPos2d : function(point, camera, dom){//获取一个三维坐标对应屏幕中的二维坐标
		var camera = camera || player.camera;
		var dom = dom || player.domElement;
        
        
        if(!camera)return
		var pos = point.clone().project(camera)	//比之前hotspot的计算方式写得简单  project用于3转2(求法同shader)； unproject用于2转3 :new r.Vector3(e.x, e.y, -1).unproject(this.camera);
		
		var x,y;
		x = (pos.x + 1) / 2 * dom.clientWidth;
		y = (1 - (pos.y + 1) / 2) * dom.clientHeight; 
  
		var inSight = x <= dom.clientWidth &&  x >= 0    //是否在屏幕中   
					&& y <= dom.clientHeight &&  y >= 0 
	 
	
		return {
			pos: new THREE.Vector2(x,y),  // 屏幕像素坐标
			vector:  pos,   //（范围 -1 ~ 1）
			trueSide : pos.z<1, //trueSide为false时，即使在屏幕范围内可见，也是反方向的另一个不可以被渲染的点   参见Tag.update
			inSight : inSight	//在屏幕范围内可见
		};
	},

	ifShelter: function (pos3d,  pos2d,  camera, floorIndex) {
        //检测某点在视线中是否被mesh遮挡
        if (!pos2d) pos2d = convertTool.getPos2d(pos3d )
        camera = camera || player.camera
        var ori = new THREE.Vector3(pos2d.x, pos2d.y, -1).unproject(camera) //找到视线原点
        var dir = pos3d.clone().sub(ori).normalize()
        var ray = new THREE.Raycaster(ori, dir); //由外向里 因为模型从内侧是可见的所以从外侧
	
		/* if(config.isEdit && publicObjectSet.editor.mainDesign.editing){
			var o = ray.intersectObjects(publicObjectSet.editor.mainDesign.wallMeshes);
		}else{ */
        let colliders = player.model.allFloorsVisible ? player.model.colliders : (floorIndex != void 0 ? player.model.floors.index[floorIndex] : player.model.currentFloor).collider.children
        //let colliders = (floorIndex == void 0 ) ? player.model.colliders : player.model.floors.index[floorIndex].collider.children
        var o = ray.intersectObjects(colliders); 
		//} 
		var len = pos3d.distanceTo(ori);
		if (o && o.length) {
			for(var i=0;i<o.length;i++){
				if(o[i].distance < len){  return true;  }//有遮挡
			} 
		} 
	},
    
    
    /* 
        拖拽时，获取鼠标在拖拽面上的位置（需要借助另一个intersectPlane面来计算，即和相机方向一样的面，可保证铺满屏幕）
        但是不一定能获取到，比如鼠标射线不朝向拖拽面时，即使获取也会是一个意外的反方向的交点。
	 */
	getPosAtPlane : function(pos, info/* , mouse, camera */){ //pos:与intersectPlane的交点 见笔记
		var A = pos; 
        var player = player;      
        var mouse = player.mouse;
        var O = new THREE.Vector3(mouse.x, mouse.y, -1).unproject(player.camera);
		
		
		if(info.y != void 0){//地面线的
        
            var y = info.y; 
            
            if(player.mode == "floorplan"/*  ||  Math.abs(O.x-pos.x)<0.0001 && Math.abs(O.z-pos.z)<0.0001) */){
            //intersectPlane和地面平行，无交点
                var x = pos.x, z = pos.z;
            
            }else{
             
                if(y<player.camera.position.y && O.y <= A.y /* || y>player.camera.position.y && O.y >= A.y  */)return null;  //鼠标射线向上。因为相机一定位于地面以上（地面不会抬高到相机上吧？），所以无交点。
                if(O.y == A.y){console.log('一样？？');return;}
                if(A.y == y){console.log('一样2？？');return;}
                var r = (O.y-y)/(A.y-y);
                var x = (r*A.x-O.x)/(r-1);
                var z = (r*A.z-O.z)/(r-1); 
			}
		}else{//垂直的也有越过消失点以后反向变化的情况，但使用时影响不大
			var N = info.normalVec;
			var P = info.pullPos;
			if(N.y != 0 ){console.log('N.y != 0');return;} //仅仅支持垂直于地面的的墙壁，目前都是
			if(O.z==A.z){console.log('O.z==A.z？');return;}
			if(N.z!=0 && N.x != 0){//直接用这个通用的也可以，支持斜线的墙
				//console.log('N.z==0 && N.x == 0？');  
				var c = ( N.x*(A.x-O.x) + N.y*(A.y-O.y) + N.z*(A.z-O.z));
				if(c == 0){console.log("分母为0？？ return;");return;} 
				var t = -((N.x*O.x + N.y*O.y + N.z*O.z) - (P.x*N.x + P.y*N.y + P.z*N.z) ) / c
				var x = t * (A.x - O.x) + O.x;
				var y = t * (A.y - O.y) + O.y;
				var z = t * (A.z - O.z) + O.z;
				/*原理： 已知空间直线L：(x-a)/m=(x-b)/n=(z-c)/p和空间平面π：Ax+By+Cz+D=0;
				求直线L与平面π的交点的坐标。
				把直线方程改写成参数形式：设(x-a)/m=(x-b)/n=(z-c)/p=t；
				则x=mt+a；y=nt+b；z=pt+c；代入平面π的方程得：
				A(mt+a)+B(nt+b)+C(pt+c)+D=0
				由此解得t=-(Aa+Bb+Cc+D)/(Am+Bn+Cp)
				再代入参数方程即得交点的坐标(x，y，z). */
			}else if(N.x ==0 ){ //z与pullPos相等
				var z = P.z;
				if(O.y == A.y){console.log('一样？？');return;}
				if(A.y == y){console.log('一样2？？');return;}
                if(A.z == z){console.log('一样3？？');return;}
				var r = (O.z-z)/(A.z-z);
				var x = (r*A.x-O.x)/(r-1);
				var y = (r*A.y-O.y)/(r-1);
			}else if(N.z == 0){//x与pullPos相等
				var x = P.x;
				if(O.y == A.y){console.log('一样？？');return;}
				if(A.y == y){console.log('一样2？？');return;}
                if(A.x == x){console.log('一样3？？');return;}
				var r = (O.x-x)/(A.x-x);
				var y = (r*A.y-O.y)/(r-1);
				var z = (r*A.z-O.z)/(r-1);
			}
		}   
		
		return new THREE.Vector3(x,y,z);
	},


	
	getMouseIntersect : function(camera, meshes, mouse){//获取鼠标和meshes交点
		var raycaster = new THREE.Raycaster; 
		camera.updateMatrixWorld(); 
		var origin = new THREE.Vector3(mouse.x,mouse.y,-1).unproject(camera)
		  , end = new THREE.Vector3(mouse.x,mouse.y,1).unproject(camera); 
		var dir = end.sub(origin).normalize() 
		raycaster.set(origin, dir);
		var n = raycaster.intersectObjects(meshes);
		if (0 === n.length)
			return null;
		return n[0];
		
	},
	ifIntersectChunks : function(A,B,options={}){//获取某个线段/射线和meshes的交点 
		var dir = B.clone().sub(A).normalize();
		var len = options.InfinityLen ? Infinity :  A.distanceTo(B) + (options.extLen||0);
		var ray = new THREE.Raycaster(A.clone(), dir, 0, len);
          
		var o = ray.intersectObjects(options.model || player.model.colliders);
		if (o && o.length)return o;
		  
		if(options.throughWidth){ //允许最小宽度，防止穿过极小的缝隙导致撞墙感
			var normal = math.getNormal({points:[{x:A.x, y:A.z},{x:B.x, y:B.z}]});//线段法线
			normal.multiplyScalar(options.throughWidth)
			var normalVec3 = new THREE.Vector3(normal.x, 0, normal.y);
			
			var A2 = A.clone().add(normalVec3)
			ray.set(A2, dir);  
			var o2 = ray.intersectObjects(options.model || player.model.colliders);
			ray.set(A.clone().add(normalVec3.negate()), dir);
			if (o2 && o2.length)return o2;
			var o3 = ray.intersectObjects(options.model || player.model.colliders);
			if (o3 && o3.length)return o3; 
		} 
		return null;
	},
    
	getPosAtSphere : function(pos3d, toPanoPos){
		var dir = pos3d.clone().sub(toPanoPos); 
		dir.normalize();//然后计算在球中
		dir.multiplyScalar(Constants.skyRadius);   
		dir.add(toPanoPos); 
		return dir;
	} ,
    
    getScaleForConstantSize : function(op={}){ //获得规定二维大小的mesh的scale值。可以避免因camera的projection造成的mesh视觉大小改变。  来源：tag.updateDisc
        var w;  
        var i = new THREE.Vector3, o = new THREE.Vector3, l = new THREE.Vector3, c = new THREE.Vector3, h = new THREE.Vector3
         
        if(!op.resolution){
            let renderSize = player.sceneRenderer.renderer.getSize()
            op.resolution = {x:renderSize.width, y:renderSize.height} 
        } 
        if(!op.camera){
            let camera = player.cameraControls.activeControl ? player.cameraControls.activeControl.camera : player.camera
            let camera2 = camera.clone(); 
            camera2.matrixWorld.copy(player.camera.matrixWorld) //因为其他原因该camera的matrixWorld被还原了，而player.camera的是正确的
            op.camera = camera2
        }
         
        if(op.width2d) w = op.width2d //如果恒定二维宽度
        else{//否则考虑上距离，加一丢丢近大远小的效果
            var currentDis, nearBound, farBound
            if(op.camera.type == "OrthographicCamera"){
                currentDis = (op.camera.right - op.camera.left) / op.camera.zoom
            }else{
                currentDis = op.position.distanceTo(op.camera.position);
            } 
            w = op.maxSize - ( op.maxSize -  op.minSize) * THREE.Math.smoothstep(currentDis,  op.nearBound,  op.farBound);
            //maxSize ： mesh要表现的最大像素宽度；   nearBound： 最近距离，若比nearBound近，则使用maxSize
        }
        i.copy(op.position).project(op.camera),  //tag中心在屏幕上的二维坐标
        o.set(op.resolution.x / 2, op.resolution.y / 2, 1).multiply(i), //转化成px   -w/2 到 w/2的范围
        l.set(w / 2, 0, 0).add(o),  //加上tag宽度的一半
        c.set(2 / op.resolution.x, 2 / op.resolution.y, 1).multiply(l), //再转回  -1 到 1的范围
        h.copy(c).unproject(op.camera);//再转成三维坐标，求得tag边缘的位置
        var g = h.distanceTo(op.position)//就能得到tag的三维半径
    
        return g  //可能NAN  当相机和position重叠时
         
    } ,
    
    updateVisible : function(object, reason, ifShow, level=0, type){//当所有加入的条件都不为false时才显示. reason='force'一般是强制、临时的
        if(!object.unvisibleReasons) object.unvisibleReasons = []; //如果length>0代表不可见
        if(!object.visibleReasons) object.visibleReasons = []; //在同级时，优先可见
        
        
        var update = function(){
            
            //先按从高到低的level排列
            object.unvisibleReasons = object.unvisibleReasons.sort((a,b)=>b.level-a.level)
            object.visibleReasons = object.visibleReasons.sort((a,b)=>b.level-a.level)
            var maxVisiLevel = object.visibleReasons[0] ? object.visibleReasons[0].level : -1
            var maxunVisiLevel = object.unvisibleReasons[0] ? object.unvisibleReasons[0].level : -1
            
            var shouldVisi = maxVisiLevel >= maxunVisiLevel
            var visiBefore = object.visible
            
            
            if(visiBefore != shouldVisi){
                object.visible = shouldVisi
                object.dispatchEvent({
                    type: 'isVisible',
                    visible: shouldVisi,
                    reason,
                }) 
            }
            
            
        }    
        
        
        
        if(ifShow){ 

            var index = object.unvisibleReasons.findIndex(e=>e.reason == reason) 
            if(index > -1){
                type = 'cancel'
                object.unvisibleReasons.splice(index, 1); 
            }
            
            if(type == 'add' ){
                if(!object.visibleReasons.some(e=>e.reason == reason)){
                    object.visibleReasons.push({reason,level})
                }
            } 
        }else{ 
            var index = object.visibleReasons.findIndex(e=>e.reason == reason) 
            if(index > -1){
                type = 'cancel'
                object.visibleReasons.splice(index, 1); 
            }
            
            if(type != 'cancel' ){
                if(!object.unvisibleReasons.some(e=>e.reason == reason)){
                    object.unvisibleReasons.push({reason,level})
                }
            }
        }
          
        update() 
        
    }, 
     
         
         
         
    getObjVisiByReason : function(object,reason){//获取在某条件下是否可见.  注： 用户在数据集选择可不可见为"datasetSelection"
        if(object.visible)return true
        else{
            return !object.unvisibleReasons || !object.unvisibleReasons.some(e=>e.reason == reason)  
        }
    } 
        
}

 

window.browser = { //提前定义
    isMobile: function() {
        var e = navigator.userAgent || navigator.vendor || window.opera;
        return /(android|bb\d+|meego).+mobile|android|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od|ad)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(e) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(e.substr(0, 4))
    },
    isFullscreen: function() {
        return document.fullscreenElement || document.mozFullscreenElement || document.mozFullScreenElement || document.webkitFullscreenElement || document.msFullscreenElement
    },
    
}

window.expandCommon = function(common){
    Object.assign(common,{
        intervalTool : {  //延时update，防止卡顿
            list:[], 
            
            isWaiting:function(name, func, delayTime/* , autoCycle */){   
	            let item = this.list.find(e=>e.name == name);
	            if(!item){  //如果没有该项， 则加入循环
	                let ifContinue = func(); 
	                item = {name, func, delayTime}; 
	                this.list.push(item);
	                    setTimeout(()=>{ 
	                        var a = this.list.indexOf(item);
	                        this.list.splice(a,1);
	                        let {func, delayTime} = item; 
	                        if(item.requestUpdate || ifContinue )  this.isWaiting(name, func, delayTime); //循环
	                    },delayTime);
	                 
	            }else {//如果有该项，说明现在请求下一次继续更新 
                    //更新属性  
                    item.func = func;
                    item.delayTime = delayTime;
                    item.requestUpdate = true;  
	            }
	        },
        },
        
        sortByScore : function(list, request, rank) {
            var i = request ? common.filterAll(list, request) : list
            return 0 === i.length ? null : i = i.map(function(e) {
                let scores = rank.map(function(f){return f(e)}) //add
                
                return {
                    item: e,
                    scores,
                    score: scores.reduce(function(t, i) {
                        return t + i
                    }, 0)
                }
            }).sort(function(e, t) {
                return t.score - e.score;
            })
        },
        
        getVisiblePano : function(positions = [], panos, options={}){//add
            var visiblePanos = []; 
           
           
            options.posAtPanos = options.posAtPanos  || {}//在不同漫游点的positions
            panos.forEach((pano)=>{
                if(!pano.isAligned())return;
                var A = pano.position.clone();
                var posB = options.posAtPanos[pano.id] || positions;
                var posLength = posB.length
                
                for(let i=0;i<posLength;i++){
                    
                    var B = posB[i];
                    var ray = new THREE.Raycaster(A.clone(), B.clone().sub(A).normalize(), 0, A.distanceTo(B) - (options.tolerance||0) ) 
                    var o = ray.intersectObjects(options.model || player.model.colliders, true);
                
                    if (!o || !o.length){ //只要有一点可见，就算整体可见
                        visiblePanos.push(pano.id);
                        break;
                    }
                }    
                 
            })  	  
         
            return visiblePanos
        } 
        
    })
    
}







//--------------------------------------
//管理js文件 获取modeldata.js 判断是否有特殊的字段，如果有就先加载SpecialScene.js 里面有对特殊场景处理的代码 否则就直接加载main



var Manage = function(){
    this.weixinURL = "https://res.wx.qq.com/open/js/jweixin-1.2.0.js",
    this.time = "?"+new Date().getTime();
    this.loadAudio();
    this.loadWeixin();
}
//动态加载js文件
Manage.prototype.LoadJs = function(_files, succes){
	/* 已加载文件缓存列表,用于判断文件是否已加载过，若已加载则不再次加载*/
	var classcodes = [];
	var FileArray = [];
    if (typeof _files === "object") {
        FileArray = _files;
    } else {
        /*如果文件列表是字符串，则用,切分成数组*/
        if (typeof _files === "string") {
            FileArray = _files.split(",");
        }
    }
    if (FileArray != null && FileArray.length > 0) {
        var LoadedCount = 0;
        for (var i = 0; i < FileArray.length; i++) {
            loadFile(FileArray[i], function() {
                LoadedCount++;
                if (LoadedCount == FileArray.length) {
                	try {
                		succes();
                	}
                    catch(err) {
					    console.log("err: 您未定义回调");
					}
                }
            })
        }
    }
    /*加载JS文件,url:文件路径,success:加载成功回调函数*/
    function loadFile(url, success) {
        if (!FileIsExt(classcodes, url)) {
            var _ThisType = GetFileType(url);
            var ThisType = _ThisType.indexOf("?") == -1 ? _ThisType  :  _ThisType.substring(0,_ThisType.indexOf("?"));
            var fileObj = null;
            if (ThisType == ".js") {
                fileObj = document.createElement('script');
                fileObj.src = url;
            } else if (ThisType == ".css") {
                fileObj = document.createElement('link');
                fileObj.href = url;
                fileObj.type = "text/css";
                fileObj.rel = "stylesheet";
            } else if (ThisType == ".less") {
                fileObj = document.createElement('link');
                fileObj.href = url;
                fileObj.type = "text/css";
                fileObj.rel = "stylesheet/less";
            }
            success = success || function() {};
            fileObj.onload = fileObj.onreadystatechange = function() {
                if (!this.readyState || 'loaded' === this.readyState || 'complete' === this.readyState) {
                    success();
                    classcodes.push(url)
                }
            }
            document.getElementsByTagName('head')[0].appendChild(fileObj);
        } else {
            success();
        }
    }
    /*获取文件类型,后缀名，小写*/
    function GetFileType(url) {
        if (url != null && url.length > 0) {
            return url.substr(url.lastIndexOf(".")).toLowerCase();
        }
        return "";
    }
    /*文件是否已加载*/
    function FileIsExt(FileArray, _url) {
        if (FileArray != null && FileArray.length > 0) {
            var len = FileArray.length;
            for (var i = 0; i < len; i++) {
                if (FileArray[i] == _url) {
                    return true;
                }
            }
        }
        return false;
    }
	
};

//获取页面url后面的参数
Manage.prototype.number = function(variable) {
	var query = window.location.search.substring(1);
   	var vars = query.split("&");
   	for (var i=0;i<vars.length;i++) {
           	var pair = vars[i].split("=");
           	if(pair[0] == variable){return pair[1];}
   	}
   	return(false);
};

Manage.prototype.loadWeixin = function() {
    var that = this;
    this.LoadJs(that.weixinURL+that.time,function(){ });

}


 


Manage.prototype.weixinShare = function() {
    console.log("weixinShare")
    $.ajax({    
        url:'https://www.4dage.com/wechat/jssdk/', 
        type: "post",
        data : {
            'url' : location.href.split('#')[0]
        }, 
        dataType:"jsonp",     
        jsonpCallback:"success_jsonp",         
        success:function(data,textStatus){
            console.log("weixinShare success")
            console.log(data.appId)
            
            wx.config({
                // debug : true,
                appId : data.appId,
                timestamp : data.timestamp,
                nonceStr : data.nonceStr,
                signature : data.signature,
                jsApiList : [ 'checkJsApi', 'onMenuShareTimeline',
                        'onMenuShareAppMessage', 'onMenuShareQQ',
                        'onMenuShareWeibo', 'hideMenuItems',
                        'showMenuItems', 'hideAllNonBaseMenuItem',
                        'showAllNonBaseMenuItem', 'translateVoice',
                        'startRecord', 'stopRecord', 'onRecordEnd',
                        'playVoice', 'pauseVoice', 'stopVoice',
                        'uploadVoice', 'downloadVoice', 'chooseImage',
                        'previewImage', 'uploadImage', 'downloadImage',
                        'getNetworkType', 'openLocation', 'getLocation',
                        'hideOptionMenu', 'showOptionMenu', 'closeWindow',
                        'scanQRCode', 'chooseWXPay',
                        'openProductSpecificView', 'addCard', 'chooseCard',
                        'openCard' ]
            });
        },    
        error:function(XMLHttpRequest,textStatus,errorThrown){    
            console.log("jsonp.error:"+textStatus);    
        }    
    }); 
    
    var success_jsonp = function(json){
        console.log(json);
    };


    wx.ready(function(){ 
        // config信息验证后会执行ready方法，所有接口调用都必须在config接口获得结果之后，config是一个客户端的异步操作，所以如果需要在页面加载时就调用相关接口，则须把相关接口放在ready函数中调用来确保正确执行〿
        //对于用户触发时才调用的接口，则可以直接调用，不需要放在ready函数中〿
        //分享到朋友圈 
        console.log(g_weixinObj)
        wx.onMenuShareTimeline({ 
            title: g_weixinObj.title, // 分享标题 
            link: g_weixinObj.lineLink, // 分享链接 
            imgUrl: g_weixinObj.imgUrl, // 分享图标 
            desc: g_weixinObj.desc
        }); 

        //获取“分享给朋友”按钮点击状态及自定义分享内容接叿
        wx.onMenuShareAppMessage({ 
            title: g_weixinObj.title, // 分享标题 
            desc: g_weixinObj.desc,  // 分享描述 
            link: g_weixinObj.lineLink, // 分享链接 
            imgUrl: g_weixinObj.imgUrl, // 分享图标 
            type: '', // 分享类型,music、video或link，不填默认为link 
            dataUrl: '' // 如果type是music或video，则要提供数据链接，默认为空 
        });
        
        wx.onMenuShareWeibo({
            title: g_weixinObj.title, // 分享标题
            desc: g_weixinObj.desc, // 分享描述
            link: g_weixinObj.lineLink, // 分享链接
            imgUrl: g_weixinObj.imgUrl, // 分享图标
            success: function () { 
                // 用户确认分享后执行的回调函数
            },
            cancel: function () { 
                // 用户取消分享后执行的回调函数
            }
        });

        wx.onMenuShareQZone({
            title: g_weixinObj.title, // 分享标题
            desc: g_weixinObj.desc, // 分享描述
            link: g_weixinObj.lineLink, // 分享链接
            imgUrl: g_weixinObj.imgUrl, // 分享图标
            success: function () { 
                // 用户确认分享后执行的回调函数
            },
            cancel: function () { 
                // 用户取消分享后执行的回调函数
            }
        });

        wx.onMenuShareQQ({
            title: g_weixinObj.title, // 分享标题
            desc:  g_weixinObj.desc, // 分享描述
            link: g_weixinObj.lineLink, // 分享链接
            imgUrl: g_weixinObj.imgUrl, // 分享图标
            success: function () { 
                // 用户确认分享后执行的回调函数
            },
            cancel: function () { 
                // 用户取消分享后执行的回调函数
            }
        });

        wx.error(function(res){ 
        // config信息验证失败会执行error函数，如签名过期导致验证失败，具体错误信息可以打开config的debug模式查看，也可以在返回的res参数中查看，对于SPA可以在这里更新签名〿

        });
    });

    
}



Manage.prototype.dealURL = function(src, type){
    //music: "///super.4dage.com/data/LYW/edit/20200928_151633415.mp3"
    //"https://super.4dage.com/data/LYW/edit/20200928_165319399.jpg"]
    
   
    if(window.isLocal && settings.localPrefix!=void 0){//本地将线上的前缀替换
        var oldPrefix = 'super.4dage.com/';  //最简单的地址写一个，如果有其他完全不一样的地址就用数组逐个判断
        var index = src.indexOf(oldPrefix);
        if(index>-1){
            var wholeOldPrefix = src.slice(0, index+oldPrefix.length);
            return src.replace(wholeOldPrefix, settings.localPrefix)
        }else console.error("没有找到合适的本地链接") 
        return src
    }else{
        //add https://
        var prefix = g_Prefix.replace('https://','').replace('http://','') 
        if(!src.includes('http:/') && !src.includes('https:/') && src.includes(prefix)){
            src = 'https://'+src
        }
        return src
    }
    
    
}


Manage.prototype.removeSrcPostMark = function(url){//去除texture.load时自动加上的'?'
    var index = url.indexOf('?')
    if(index>-1){
        return url.slice(0, index)
    }else return url 
}


Manage.prototype.showInfo = function (o) { // ({result:true, title:"发布成功"}); 
	 
	var box = $(".resultBox");
	
	var title = o.title || o || i18n.get('保存成功');
	box.children().eq(0).html(title) 
	
	
	//var time = o.time || THREE.Math.clamp((Config.lang=='en') ? title.length*50 : title.length*130 ,1300,5000);
	var time = o.time ||  THREE.Math.clamp(title.length*130 ,1300,  5000);
    o.time || console.log("showtime  " + time)
	
	//实际有一半的时间在渐变透明度
	this.showInfoTimer && clearTimeout(this.showInfoTimer) 
	box.removeClass("animate");//如果之后不久又要showinfo一个的话，先停止前面的animate
	setTimeout(function () {
		box.css(
			{
				'-webkit-animation-duration': time + 'ms',
				'animation-duration': time + 'ms'
			}
		)
        if(o.top){
            box.children().css('top', o.top + "%");
        }else{
            box.children().css('top', '' )
        }
		box.removeClass("hide");
		box.addClass("animate");
		if (o.dontInteract) {//遮挡对屏幕的操作
			box.css('pointer-events', 'auto')
		} else {
			box.css('pointer-events', 'none')
		}
		 

		this.showInfoTimer = setTimeout(function () {
			box.removeClass("animate");
			box.addClass("hide");
			this.showInfoTimer = null; 
		}.bind(this), time + 20)

	}.bind(this), 50)//这个数字太小的话后面触发的没有重新animate的效果 应该要比帧率大吧


}//like:  manage.showInfo({title:'a', top:20})




//公用的函数

function getQueryVariable(variable)
{
       var query = window.location.search.substring(1);
       var vars = query.split("&");
       for (var i=0;i<vars.length;i++) {
               var pair = vars[i].split("=");
               if(pair[0] == variable){return pair[1];}
       }
       return(false);
}

//隐藏公司Logo
function showLogo(){
    $("#myCompany").hide();
    $("#loaderCoBrandName").hide();
    $("#title-logo").hide();
    $(".title-container").css("justify-content","center")
}
//czj 添加随机的时间
function randomTime(){
    return new Date()
};
function matcher(data){
    if(!data || !g_version ) return data;
    delete data.model.vision_version;
    var _data = {
        files: {
            "templates": ["images/images{{number}}/{{filename}}"]
        },
        model :{
            sid :window.number,
            camera_start:
            data.model.images && data.model.images.length != 0  ? 
            {
                camera: {
                    zoom: "-1",
                    quaternion: [
                        JSON.parse(data.model.images[0].metadata).camera_quaternion.z,
                        JSON.parse(data.model.images[0].metadata).camera_quaternion.w,
                        JSON.parse(data.model.images[0].metadata).camera_quaternion.x,
                        JSON.parse(data.model.images[0].metadata).camera_quaternion.y
                    ]
                },
                pano: { uuid: JSON.parse(data.model.images[0].metadata).scan_id },
                mode: "0"
            }
            : ''
        },
        sid: window.number,
        hoticon: {
            default: "https://super.4dage.com/images/4dagePoint2.png",
            higt: "https://super.4dage.com/images/4dagePoint.png"
        },
        special: "false",
        weixinDesc: ""
    };
    $.extend(true,data,_data)
    return data;
}

function hotMatcher(data={}){
    //if(!data || !g_version) return data; 
    if(g_version) {
        data.tourAudio = data.audio || {};
    }else{
        data.tourAudio = {}
    }
    return data;
}


var GifTexDeal = {
    
    animateObjects : [], 
    animateTexs : [] ,
    addAnimation : function(texture, owner, info, id){
        /* if(this.animateObjects.find(e=> 
            e.texture == texture && !ifSame(info, e.info) 
        )) */ 
        
        var animation
        var tex = this.animateTexs.find(e=>e.texture == texture)
        if(tex){
            animation = tex
        }else{
            animation = {texture,info }
            this.animateTexs.push(animation)
            this.setRepeart(animation)
        }
        
        var object = {
            animation, //默认相同的texture对应的info是一样的, 对应一个animation
            owner,        
        }
         
        this.animateObjects.push(object)
        return object
    },
    
    
    remove : function(object){
        var index = this.animateObjects.indexOf(object) 
        if(index>-1){
            this.animateObjects.splice(index, 1)
            
            if(!this.animateObjects.find(e=>e.animation == object.animation)){
                let i = this.animateTexs.indexOf(object.animation)
                this.animateTexs.splice(i, 1)
                object.animation.texture.repeat.set(1,1) 
            }
            
            this.stop(object) 
            
        }
    },
    setRepeart : function(animation){
        animation.texture.repeat.set(1/animation.info.cellXcount, 1/animation.info.cellYcount)
    },
    start: function(object){ 
         
        if(!object || object.started )return;
        object.started = true
        if(object.animation.started)return;
        object.animation.started = true
        
        var info = object.animation.info
        var count = info.cellXcount * info.cellYcount - (info.voidCount || 0)
     
        if(count <= 1)return;
        
        transitions.start( (progress)=>{
            var index = Math.floor((count-1) * progress);
            var indexX =  index % info.cellXcount
            var indexY =  info.cellYcount - Math.floor(index /info.cellXcount ) - 1;  //uv.offset.y是从下到上的
            object.animation.texture.offset.x = indexX / info.cellXcount;
            object.animation.texture.offset.y = indexY / info.cellYcount;
            
            //console.log(object.id + " : "+ object.texture.offset.toArray())
        } , info.duration * (-1), null,/* ()=>{//done  (-1):循环
            object.started = false
            object.texture.offset.x = 0;
            object.texture.offset.y = 0;
            this.start(object)
        }, */ 0 ,null, object.id, "gif_"+object.animation.texture.id); 

        

    },
    
    
    stop: function(object){ 
        if(!object || !object.started)return;
        object.started = false
        //只有该object对应的texture对应的所有object都停止了，才能真的停止动画:
        if(this.animateObjects.find(e=>e.animation == object.animation && e.started)) return;
        
         
        transitions.cancelById("gif_"+object.animation.texture.id);
        object.animation.texture.offset.set(0,0)
        object.animation.started = false     
        
    }
}

var CloneObject = function(copyObj, result, isSimpleCopy, extraReplace) {
    //isSimpleCopy只复制最外层
    //复制json		result的可能：普通数字或字符串、普通数组、复杂对象
    if(!copyObj)return copyObj //0 null undefined ''
    result = result || {};
    if (copyObj instanceof Array) {
        /*  if (copyObj[0]instanceof Object) {
            //不支持含有 [[Object]] 这样二级数组里面还是复杂数据的，普通和复杂的数据混合可能也不支持
            console.error("不支持含有 [[Object]] 这样二级数组里面还是复杂数据的...")
        }  
        
        return copyObj.slice(0);*/ //如果是数组，直接复制返回（排除数组内是object 
        return copyObj.map(e=>{
            if(e instanceof Object){
                return CloneObject(e)
            }else return e 
        })
       
    }else{
        if(copyObj.clone instanceof Function ){ //解决一部分
            return copyObj.clone()
        }
    }
    for (var key in copyObj) {
        if (copyObj[key] instanceof Object && !isSimpleCopy)
            result[key] = CloneObject(copyObj[key]);
        else
            result[key] = copyObj[key];
        //如果是函数类同基本数据，即复制引用
    }
    return result;
}
;
var ifSame = function(object1, object2){
    if(object1 == object2  )return true // 0 != undefined  , 0 == ''
    else if(!object1 || !object2) return false
    else if(object1.constructor != object2.constructor){
        return false
    }else if(object1 instanceof Array ) {
        if(object1.length != object2.length)return false;
        var _object2 = object2.slice(0);
        
        for(let i=0;i<object1.length;i++){ 
            var u = _object2.find(e=>ifSame(object1[i], e));
            if(u == void 0 && !_object2.includes(u) && !object1.includes(u))return false;
            else{
                let index = _object2.indexOf(u);
                _object2.splice(index,1);
            }
        }
        
        return true
    }else if(object1.equals instanceof Function ){//复杂数据仅支持这种，其他的可能卡住？
        
        return object1.equals(object2)
          
    }else if(typeof object1 == 'number' ||  typeof object1 == 'string'){
        if(isNaN(object1) && isNaN(object2))return true
        else return object1 == object2
        
    }else if(typeof object1 == "object"){
        var keys1 = Object.keys(object1)
        var keys2 = Object.keys(object2)
        if(!ifSame(keys1,keys2))return false;
        
        for(let i in object1){
            var same = ifSame(object1[i], object2[i]);
            if(!same)return false
        }
        return true
    }else{
        console.log('isSame出现例外')
    } 
    
}


function initByTHREE(THREE){ 
    
    
    window.bus = new THREE.EventDispatcher();//因为player一开始总是没创建好，所以加一个事件传递器
    
    THREE.TransitionPass =  function ( scene, camera ) {
        this.renderScene = scene;
        this.renderCamera = camera;
        
        
        this.coverRenderTarget = new THREE.WebGLRenderTarget( 100, 100, { 
            minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter,
            format: THREE.RGBAFormat 
        });
        
        this.coverTex = this.coverRenderTarget.texture; 
        
        this.enabled = false;
        
        
        this.oldClearColor = new THREE.Color();
        this.oldClearAlpha = 1;

        this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
        this.scene = new THREE.Scene();


        this.material = this.getMaskMaterial()
        var copyShader = THREE.CopyShader;
        this.materialCopy = new THREE.ShaderMaterial( {
            uniforms: this.copyUniforms,
            vertexShader: copyShader.vertexShader,
            fragmentShader: copyShader.fragmentShader,
            blending: THREE.NoBlending,
            depthTest: false,
            depthWrite: false,
            transparent: true
        } );


        this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), this.material);
        this.quad.frustumCulled = false; // Avoid getting clipped
        this.scene.add( this.quad );
        
        
    }


    THREE.TransitionPass.prototype = {//波形扩散，出下一个画面
        
        constructor: THREE.TransitionPass,
        setSize: function ( width, height ) {
            
            this.coverRenderTarget.setSize( width, height );
            
        },
        render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) {
            var oldAutoClear = renderer.autoClear; 
            renderer.autoClear = false;
            
            var uniforms = this.quad.material.uniforms
            uniforms.bgTex.value = readBuffer.texture; //更新
            uniforms.coverTex.value = this.coverTex;
            uniforms.progress.value = player.model.skybox.material.uniforms.progress.value//
            uniforms.screenRatio.value = player.domElement.clientWidth / player.domElement.clientHeight;// 使波纹为圆形
            uniforms.screenRatio.value *= uniforms.screenRatio.value
            
            renderer.render( this.scene, this.camera);
            
            renderer.autoClear = oldAutoClear;
            
            
        },
        
        start:function(sceneRenderer){
            this.enabled = true
            //draw coverTex
            this.quad.material.uniforms.progress.value = 1;
            sceneRenderer.renderer.render( sceneRenderer.scene, sceneRenderer.camera, this.coverRenderTarget, true );
            console.log('start111')
            
        },
        stop:function(){
            this.enabled = false
            console.log('stop111')
        }
        ,
        
        
        getMaskMaterial :function(){
            return new THREE.ShaderMaterial( {

                uniforms: {
                    coverTex: {
                        type: "t",
                        value: null
                    },
                    bgTex: {
                        type: "t",
                        value: null
                    },
                    progress:{
                        type: "f",
                        value: 0
                    },
                    screenRatio:{
                        type: "f",
                        value: 1
                    }
                },

                vertexShader: ` 
                    varying vec2 vUv;
                     
                    void main() 
                    {
                        vUv = uv;
                        
                        gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
                    } 
                   
                `,
                fragmentShader: ` 
                    uniform sampler2D coverTex;
                    uniform sampler2D bgTex;
                    uniform float progress; 
                    uniform float screenRatio;
                    varying vec2 vUv;
                   
                    void main() {
                        
                        const float maxRadius = 0.708;      // sqrt(0.5^2+0.5^2)
                        const float minRadius = 0.0 ;
                         
                        float radius = screenRatio>1.0 ? sqrt((vUv.x - 0.5)*(vUv.x - 0.5) + (vUv.y - 0.5)*(vUv.y - 0.5)/screenRatio) : sqrt((vUv.x - 0.5)*(vUv.x - 0.5)*screenRatio+ (vUv.y - 0.5)*(vUv.y - 0.5));
                        float diff = 0.292;    //1.0-maxRadius;
                        float radiusIn = maxRadius * progress + minRadius * (1.0-progress);
                        float radiusOut = radiusIn + diff;
                        if(radius < radiusIn) {
                            
                            gl_FragColor = texture2D(bgTex, vUv);
                            //gl_FragColor = vec4(0.0,0.0,1.0,1.0);//
                            
                        }else if(radius>radiusOut){
                            gl_FragColor = texture2D(coverTex, vUv)  ;
                            //gl_FragColor = vec4(1.0,1.0,0.0,1.0);//
                            
                        }else{
                            
                            vec4 color1 = texture2D(bgTex, vUv);
                            vec4 color2 = texture2D(coverTex, vUv);
                            float rotio = smoothstep(radiusIn ,radiusOut,radius);
                           
                            gl_FragColor = mix(color1, color2, rotio);
                        }
                    }
                 `

            } );
            
        },
        
    }

    let labels = []




    class Label2D  extends THREE.EventDispatcher{
        constructor(o={}){
            super()
            
            this.position = o.position  
            this.elem = $(o.innerHTML || '<div ><a></a></div>');
              
            $(o.domElement).append(this.elem)
            this.pos2d = new THREE.Vector3
         
            this.elem.css({position: 'absolute', 'z-index':999})
            this.clickFun = o.clickFun;
            this.clickFun && this.elem.on('click',this.clickFun.bind(this))
            
            if(o.autoUpdate){
                let update = (e)=>{if(e.cameraChanged) this.update()}
                player.on("view.changed",update) //确保player存在
                     
                
                this.addEventListener('dispose',  (e)=>{
                    player.off("view.changed",update)
                })
            } 
            this.visible = true
            this.shelterByModel = o.shelterByModel
            this.floorIndex = o.floorIndex
            labels.push(this)
             
            if(window.player.model){
                this.init()
            }else{
                let f = ()=>{
                    window.bus.removeEventListener('playerAndModelReady',f) 
                    this.init() 
                }
                window.bus.addEventListener('playerAndModelReady',f) 
                   
            }
            
        }
        
        init(){
            if(this.floorIndex != void 0){  
                player.model.on("floor.changed",(currentFloor, mode, oldFloor)=>{
                    this.update(currentFloor)  //注： currentFloor 这时候还没成为 model.currentFloor
                })
            }
        }

        
        
        update(currentFloor){
            if(!this.position || !this.visible)return
             
            var p = convertTool.getPos2d(this.position);
            if(!p || !p.trueSide){
                this.elem.css('display','none');  return;
            }
            
            //判断label是否被模型遮挡，遮挡则消失(如果是漫游模式最好提前计算visiblePanos)
            if(player.mode != 'panorama'){
                currentFloor = currentFloor || player.model.currentFloor 
                if(!player.model.allFloorsVisible && this.floorIndex != void 0 && this.floorIndex!= currentFloor.floorIndex){
                    this.elem.css('display','none');	return;
                }
                    
                if(this.shelterByModel && convertTool.ifShelter(this.position , p.vector, player.camera, this.floorIndex )){
                    this.elem.css('display','none');	return; 
                }
            }
            
            this.elem.css({
                left: p.pos.x +'px',
                top: p.pos.y +'px'
            })
            
            /* if(settings.vrEnabled){
                this.elem.css({transform:'rotate('+window.screenFaceOrient+'deg)'})
            }else{
                this.elem.css({transform:''})
            } */
            
            this.elem.css('display','block');
            this.pos2d = p.vector;
            
             
            
        }
        
        setVisible(visi, reason, level=0, type){
            convertTool.updateVisible(this, reason, visi, level, type  ) 
             
            if(!this.visible){
                this.elem.css('display','none');
            }else{ 
                this.update()
            }
        } 
        
         
        setPos(pos){
            this.position = pos;
            this.update()
        }
        
        dispose(){
            this.elem.remove();
            this._listeners = {} 
            this.dispatchEvent({type:'dispose'})
            let index = labels.indexOf(this)
            index > -1 && labels.splice(index,1) 
        
        }
         
         
    }

     



    window.Label2D = Label2D


    class RoomLabel extends Label2D{
        constructor(o){
            if(o.position instanceof Array) o.position = new THREE.Vector3().fromArray(o.position)
            o.innerHTML = `<div class="room-label" ><a><p><span>${o.title}</span></p></a></div>` 
            o.domElement = $(".widgets-doll-labels")[0]
            o.shelterByModel = true , o.autoUpdate = true

            o.clickFun = ()=>{
                if(player.roomLebelClickUnabled)return
                let result = common.sortByScore(player.model.floors.index[this.floorIndex].panos, [], [(pano)=>{
                    return -pano.position.distanceToSquared(this.position)
                }]);
                player.flyToPano({ 
                    pano : result && result[0] && result[0].item
                })
            }
             
            super(o)
            this.setTitle(o.title )
            
        }
         
        init(){
            super.init()
            //飞入后不可见
            player.on("mode.changing",(oldMode, mode, pano, duration)=>{//准备飞
                this.setStyle(oldMode, mode, duration)
            })
            this.setStyle()   
        } 
         
        setStyle(oldMode, mode, duration){
            if(!mode) mode = player.modeTran.split('-')[1] || player.mode //要变成的mode或当前mode
            if(mode == 'panorama'){
                this.setVisible(false, 'isPanorama')
            }else if(oldMode == 'panorama'){
                setTimeout(()=>{
                    this.setVisible(true, 'isPanorama')
                },duration*0.7) 
            } 
        } 
         
         
        setTitle(title){ 
            this.title = title || ''
            this.elem.html(`<a><p><span>${this.title}</span></p></a>`)
        }
        
        setEditSelect(state){//编辑页面用
            this.editing = !!state
            this.setVisible(state, 'editSelected', 1, state ? 'add':'cancel') //强制可见
        }
        
    }
    window.RoomLabel = RoomLabel

    
    
    
    /* class VideoPlayer extends THREE.EventDispatcher{
        constructor(player) {
            super() 
            this.instances = new Map() 
            this.isFlv = false 
             
        }
 
        addVideo(src) {
            let video = this._createVideo(src)
            this.instances.set(src, video)
            return video
        }

        getVideo(src) {
            let video = this.instances.get(src)

            if (!video) {
                video = this.addVideo(src)
            }

            return video
        }

        _createVideo(src) {
            let video = document.createElement('video')
            video.setAttribute('crossOrigin', 'anonymous')
            video.setAttribute('playsinline', 'true')
            video.setAttribute('x5-playsinline', 'true')
            video.setAttribute('webkit-playsinline', 'true')
            video.setAttribute('x5-video-player-type', 'h5')
            video.setAttribute('controls', 'true')
            video.setAttribute('controlslist', 'nodownload')
            video.setAttribute('preload', 'meta') //注意,src赋值就会自动加载， preload="meta" 只加载元数据，提高加载速度，否则一开始卡 
            
            video.autoplay = false
            video.muted = true
            video.loop = true
            video.style.position = 'fixed'
            video.style.left = '0'
            video.style.top = '0'
            video.style.zIndex = '1000'
            video.style.width = '200px'
            video.style.display = 'none'
            //document.body.appendChild(video) 
            //video.style.display = browser.urlHasValue('debug') ? 'block' : 'none'
            video.src = src
            
            if(this.isFlv) this.attachFlv(video)
            
            return video
        }

        attachFlv(video){  四维看看的会把视频转码成flv和mp4存在服务器， 但是大场景不会，所以安卓（如vivo）加载不了。flv必须用flvjs播放，且苹果有的支持不了
            if(video.flvjsPlayer)return
            let player = flvjs.createPlayer({ type: 'flv', url: video.src })  //需要是flv格式才行
            player.videoElement = video
            player.attachMediaElement(video)
            player.on(flvjs.Events.ERROR, this._onPlayerError.bind(this))
            player.load()
            video.flvjsPlayer = player; 
        }

        _onPlayerError() {
            console.warn('视频加载失败')
        }
        
        changeTypeToFlv(){
            this.isFlv = true
            for(let [key,value] of this.instances){ 
                this.attachFlv(value) 
            }  
        }
        
        
    } */
    
     
 

    {
        let f = ()=>{
            window.bus.removeEventListener('playerAndModelReady',f)
             
            player.on("mode.changing",(currentMode, mode, pano, duration)=>{ 
                let noLine = mode == 'floorplan';
                if(noLine){
                    $('.widgets-doll-labels').addClass('noLine').addClass('noCorner')
                }else{
                    $('.widgets-doll-labels').removeClass('noLine').removeClass('noCorner')
                } 
            })
            
            
            player.on("view.changed",(e)=>{ 
                 if(e.cameraChanged){
                     //if(needUpdateZIndex){ //如果某个label显示时需要更新index的话，加个参数
                         let label_ = labels.filter(e=>e.elem[0].style.display == 'block')
                         label_.sort((a,b)=>b.pos2d.z - a.pos2d.z)
                         label_.forEach((e,index)=>e.elem.css('z-index', index+1000)); 
                         
                     //} 
                 } 
            }) 
            
            player.model.hotGroup.children.length ? logSth() : player.on('gotHotAndStartload',logSth); 


            /* {//如果是flv格式的话  
                window.videoPlayer = new VideoPlayer() 
                
                if(browser.detectAndroidMobile()){//安卓常常播放不了
                    let scriptdom = document.querySelector('#flvJs')
                    if(scriptdom){ 
                        scriptdom.src = 'js/lib/flv.min.js'
                        scriptdom.onload = ()=>{ 
                            window.videoPlayer.changeTypeToFlv()
                            window.bus.dispatchEvent('VideoPlayerReady' ) 
                        //Hot.startLoad()
                        }
                    }
                }    
                
            } */
     
            
        } 
        
        
        window.bus.addEventListener('playerAndModelReady',f) //player model currentPano都已有 
    }
    window.bus.dispatchEvent({type: 'THREE_inited'})
}


 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
//最好能知道应该播放到的currentTime
var SoundManager = {//暂不支持同时播放
    currentAudio:null,//当前正在播放list中的哪一个
    enableSound:true,//是否允许有声音
    
    
    playHistory:[],//被打断的加入播放历史
    
    list:[ ],//同一级别可以互相打断 //暂时不做多级别
    
     
    
    play:function(name, src, currentTime){
        var object = this.list.find(e=>e.name == name)
        if(object){
            if(this.currentAudio){ 
                this.pause(this.currentAudio.name, false, true)
                
            }
            
            {//将当前要播放的播放历史中清除
                let index = this.playHistory.indexOf(object)
                if(index>-1)this.playHistory.splice(index,1);
                
            }
               
            
            this.currentAudio = object
            if(src){ 
                this.setSrc(name, src)
            }
            if(currentTime!=void 0){
                object.audio.currentTime = currentTime
            }
            if(object.audio && object.src){
                object.audio.load();	// iOS 9   还需要额外的 load 一下, 否则直接 play 无效 *///https://www.cnblogs.com/interdrp/p/4211883.html   部分资料
                object.audio.play();
                object.audio.paused || object.callback && object.callback(true)
                Log(name + '  播放 '  )
            }
            
        }
        
    },
    
    
    pause:function(name, autoReplayLast, isInterrupt){//需要能自动恢复上一个被打算的音频。恢复前判断是否还需要播放
        var object = this.list.find(e=>e.name == name)
        if(object && this.currentAudio == object){
            this.currentAudio = null
            
            
            if(object.audio){
                object.audio.pause()
                object.callback && object.callback(false)
                object.audio.src && Log(name + ' 中断音频 '+  "("+common.getFileNameFromUrl(object.audio.src)+')' )
            }
            
            
            
            if(isInterrupt){//一般主动调用不需要加这个
                this.playHistory.push(object)//如果是被中断的，加入播放历史，等待恢复播放
            }
             
            
            if(autoReplayLast){ //播放之前的音频。它们是被打断过的。
                while(this.playHistory.length){ 
                    var last = this.playHistory.pop();
                    if(last.src && last.canplay(last.audio)){
                        this.play(last.name )
                    }
                    
                }
                
            }
            
            
        }
        
    },
    
 
    
    setSrc : function(name, src){//不能直接给audio赋src！一定要用这个函数！因为我要拿这里的src来判断有无src,因为貌似audio的src会自动变，''时会变成html的链接
        var object = this.list.find(e=>e.name == name)
        object.src = src
        object.audio.src = src
        Log(`${object.name} 设置src: ${src}`)
    },
    
    createAudio:function(object={}){//name, level, canplay
        if(!object.fake){
            object.audio = new Audio(); 
            object.audio.loop = !!object.loop;
            //object.audio.autoplay = true;
        
            
            object.audio.addEventListener('ended', ()=>{ 
                if(object.loop){//循环
                    Log(`${object.name} 播放完毕，重新播放`)
                    object.audio.play()
                }else{
                    this.pause(object.name, true);//停止后的后续处理
                }
            });
            
            
            object.audio.oncanplaythrough = ()=>{ 
                Log(`${object.name} canplaythrough  `) 
            }
        }
        
        this.list.push(object)
        
    },
    
    
    
    initAutoPlay:function(){  //处理设备自动播放限制
     
    
        let play = function(){  
            if (this.currentAudio && this.currentAudio.audio && this.currentAudio.src && this.currentAudio.audio.paused ) {
                this.currentAudio.audio.play() //一般触屏了都会播放成功，就不识别paused了
                 
                this.currentAudio.callback && this.currentAudio.callback(true)
                
                Log(`${this.currentAudio.name} 自动播放成功`) //即使设置src在这之后好像也能成功播放
            }else{
                
            }
            document.removeEventListener("touchstart",play);
            document.removeEventListener("click",play);
            $('#player')[0].removeEventListener("touchstart", play);
        }.bind(this);
        
        
        document.addEventListener("WeixinJSBridgeReady", play, false);
        document.addEventListener("touchstart", play);//ios需要加个事件才能播放 不能自动播放；如果还有浏览器不行，换成别的交互事件
        document.addEventListener("click", play);
        $('#player')[0].addEventListener("touchstart", play); 
            
         
    }
}


function Log(value, color, fontSize){
    color = color || '#13f'
    fontSize = fontSize || 14
    console.warn(`%c${value}`, `color:${color};font-size:${fontSize}px`) 
}


 


Manage.prototype.loadAudio = function() { //相关：g_tourAudio \  g_playAudio
    
     
    
    
    //热点页面因为挡住了界面，所以暂时不存在别的音频阻止热点音频的可能
    //box视频都静音，所以暂时不考虑
    
    SoundManager.createAudio({
        name:'bgm',
        level:0, 
        src:'',
        loop:true,
        canplay:(audio)=>{
            return this.bgmShouldPlay
        },
        callback:(state)=>{//play或pause时随之触发的函数（即使还没开始播放）
            if(state){
                $("#volume a img").attr("src", "./images/volume_off.png")
                $("#volume").attr("title", "关闭声音");
            }else{
                $("#volume a img").attr("src", "./images/volume_on.png")
                $("#volume").attr("title", "打开声音");
            }
        }
    })  
    SoundManager.createAudio({
        name:'tour',
        level:0, 
        src:'',
        loop:false,
        canplay:(audio)=>{
            return player.director && player.director.tourIsPlaying && player.director.getAudio()
        }
    })
    SoundManager.createAudio({
        name:'hot',
        fake:true,//实际上没有audio. 只是为了来停止和续播其他音频
        level:1, 
        src:'',
        loop:false,
        canplay:(audio)=>{
            
        }
    })
     
    
    $("#volume").find("a").on("click", ()=> {  
        if($("#volume img")[0].src.indexOf("_on.png")>-1)
        { 
            this.switchBgmState(true); 
        }
        else if($("#volume img")[0].src.indexOf("_off.png")>-1)
        {
            this.switchBgmState(false);    
        }
    })  
    
    this.switchBgmState(true);//初始设置允许播放bgm 
    SoundManager.initAutoPlay()
    
    
}  


 
Manage.prototype.switchBgmState = function(state){//按钮的状态完全代表是否应该播放bgm，即使还没加载完
    this.bgmShouldPlay = state
    
    
    if(state){
        
        SoundManager.play('bgm')
    }else{
        
        SoundManager.pause('bgm')
    }
    
    
    /* if(!g_bgAudio || !g_bgAudio.src) return;
    
    
    var played = function(){
        console.log('begin play bgm');
        g_play = 1; 
        g_playAudio = g_bgAudio;
        
        g_tourAudio && g_tourAudio.pause()
    }
    var paused = function(){ 
        g_play = 0;
        g_playAudio == g_bgAudio && (g_playAudio =  null)
        
    }
    
    if(state ){
        g_bgAudio.play(); 
        if(g_bgAudio.paused){
            paused()
        }else{
            played()
            return true
        }            
    }else{
        g_bgAudio.pause();
        paused()
    } 
    
    
    g_bgAudio.pauseByHot = false
    g_bgAudio.pauseByTour = false */
}    
    
    
    
    
    
var manage = new Manage();

 
 

//处理cursor优先级


var CursorDeal = {
    priorityEvent : [//在前面的优先级高
        {"noIntersect":'not-allowed'},
        {"addHot":'cell'},  
        {"hoverFootIcon":'pointer'},
        {"hoverHot":'pointer'},
        {"addLabel":'cell'},
        {"moveLabel":'grab'},
    ], 
    domElements :[$('#player')[0]],
    list:[], //当前存在的cursor状态
    currentCursorIndex:null,
    
    /* init : function(viewer){
        
        this.priorityEvent.forEach(e=>{//刚开始Potree.resourcePath没值,现在换
            for(let i in e){
                e[i] = Common.replaceAll(e[i],'{Potree.resourcePath}',Potree.resourcePath)
            }
        })
          
        this.domElements = [viewer.renderArea, viewer.mapViewer.renderArea];
        
        viewer.addEventListener("CursorChange",(e)=>{
            if(e.action == 'add'){
                this.add(e.name)
            }else{
                this.remove(e.name)
            } 
        })
        
        
    }, */
    
    
    add : function(name){
        var priorityItem = this.priorityEvent.find(e=>e[name])
        if(!priorityItem){
            console.error('CursorDeal  未定义优先级 name:'+ name);
            return
        }
        
        
        if(!this.list.includes(name)){
            
            this.judge({addItem: priorityItem, name})
            
            this.list.push(name)
        }
         
    },
    
    
    remove : function(name){
        var index = this.list.indexOf(name);
        if(index > -1){
            this.list.splice(index, 1)
            this.judge()
        }
        
        
        
    },
    
    judge:function(o={}){
        //console.log(o,this.list)
        if(o.addItem){
            var addIndex = this.priorityEvent.indexOf(o.addItem) 
            if(addIndex < this.currentCursorIndex || this.currentCursorIndex == void 0){ 
                this.domElements.forEach(e=>e.style.cursor = o.addItem[o.name] )
                this.currentCursorIndex = addIndex
            }  
        }else{
            var levelMax = {index:Infinity, cursor:null }
            this.list.forEach(name=>{
                var priorityItem = this.priorityEvent.find(e=>e[name])
                var index = this.priorityEvent.indexOf(priorityItem)
                if(index < levelMax.index){
                    levelMax.index = index;
                    levelMax.cursor = priorityItem[name]
                }
            })
            this.currentCursorIndex = levelMax.index
            this.domElements.forEach(e=>e.style.cursor = levelMax.cursor || '')
        }
        
    }
    
     
}





 













function logSth(){
    let hotCount = player.model.hotGroup.children.length

    let videoCount = player.model.hotGroup.children.filter(e=>e.texType == 'video').length
    let photoCount = player.model.hotGroup.children.filter(e=>e.texType == 'photo').length
    let shineCount = player.model.hotGroup.children.filter(e=>e.texType == 'shine').length
    let aniCount = player.model.hotGroup.children.filter(e=>e.info.animateInfo).length
    let objCount = player.model.hotGroup.children.filter(e=>e.objObject).length

    let chunkLen = player.model.chunks.length
    let vertexC = player.model.chunks.reduce(function(total, chunk ){return total+chunk.geometry.attributes.position.count}, 0) 

    let panoCount = player.model.panos.list.length
    Log(`共有chunk个数 ${chunkLen} ( 顶点数 ${vertexC} )
    热点个数 ${hotCount} ( vedio ${videoCount} 个，photo ${photoCount} 个，shine ${shineCount}个。 gif ${aniCount} 个，obj ${objCount} 个
    漫游点数 ${panoCount} 个)`, '#FF4399', 14)
}

window.sceneFrom = number.slice(0,3) == 'KJ-' ? 'kankan' : '' //看看or看见转来的

//兼容一代的場景
//請求地址統一管理
var g_onePregix = "https://bigscene.4dage.com/" //对应一代  http://www.4dmodel.com/SuperPanoramic/index.html?m=55
var g_version = manage.number("version");
g_version === "one" ? g_Prefix = g_onePregix : '';

